Skip to content

Add ECDSA Support (#530) - #531

Open
madaster97 wants to merge 6 commits into
node-saml:masterfrom
madaster97:master
Open

Add ECDSA Support (#530)#531
madaster97 wants to merge 6 commits into
node-saml:masterfrom
madaster97:master

Conversation

@madaster97

@madaster97 madaster97 commented Jan 31, 2026

Copy link
Copy Markdown

Adds the feature I requested in #530 , support for the following algorithms (both signing and verifying):

I worked backwards from this example dotnet implementation (had a sample ecdsa-sha256 document). I added that sample document in a unit test as external confirmation that we verify these signatures correctly, and then made paired sign and verify - ecdsa tests that show that our signatures verify correctly.

Thanks to @uladkasach, for this stack overflow answer that showed how to call the crypto APIs correctly.

While I was here, I also touched up example.js, which was missing some config requirements for computeSignature, like:

  • sig.canonicalizationAlgorithm
  • sig.signatureAlgorithm
  • addReference > digestAlgorithm
  • addReference > transforms

Summary by CodeRabbit

  • New Features

    • Added support for ECDSA-SHA256 and ECDSA-SHA512 XML signatures.
    • Added examples for signing and validating XML with certificates.
    • Added an example command for running the local XML signing workflow.
    • Included a sample signed XML document.
  • Documentation

    • Documented the newly supported ECDSA signature algorithms.
  • Tests

    • Added coverage for ECDSA signing, verification, tampering, malformed signatures, and key compatibility.

@coderabbitai

coderabbitai Bot commented Jan 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 350cdcad-4d25-4355-a165-681641832fc2

📥 Commits

Reviewing files that changed from the base of the PR and between 5a833c0 and 5018009.

📒 Files selected for processing (1)
  • test/ecdsa-signatures.spec.ts

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

The library adds ECDSA-SHA256 and ECDSA-SHA512 algorithms, registers their XML identifiers, updates public types, and adds signing examples, output fixtures, documentation, and verification tests.

Changes

ECDSA algorithm support

Layer / File(s) Summary
Algorithm implementations and public mappings
src/signature-algorithms.ts, src/signed-xml.ts, src/types.ts
Adds ECDSA-SHA256 and ECDSA-SHA512 implementations using IEEE P1363 encoding. Registers both XML algorithm identifiers and adds them to SignatureAlgorithmType.
ECDSA verification tests
test/ecdsa-signatures.spec.ts, test/static/valid_signature_ecdsa.xml
Adds tests for library-generated and external ECDSA signatures, SHA-256 and SHA-512 verification, tampering, malformed values, key mismatches, and public KeyObject verification.

Examples and documentation

Layer / File(s) Summary
Signing example workflow
example/example.js, example/local_example.js, example/result.xml, package.json
Updates the example to load a certificate, configure canonicalization and digest settings, create XML signatures, and validate signed output. Adds an npm script and a signed XML result.
Supported algorithm documentation
README.md
Lists ECDSA-SHA256 and ECDSA-SHA512 under supported signature algorithms.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: ⚪ Minimal · up to 50180

This change adds ECDSA SHA-256 and SHA-512 XML signing and verification support with coverage for valid signatures and invalid or mismatched inputs. No current merge-readiness risk is identified.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant SignedXml
  participant ECDSAKey
  participant XMLDocument
  Caller->>SignedXml: configure ECDSA algorithm and reference
  SignedXml->>XMLDocument: canonicalize referenced content
  SignedXml->>ECDSAKey: sign canonicalized content
  ECDSAKey-->>SignedXml: return IEEE P1363 signature
  SignedXml-->>Caller: return signed XML
  Caller->>SignedXml: load signature and public certificate
  SignedXml->>ECDSAKey: verify SignatureValue
  ECDSAKey-->>SignedXml: return verification result
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise, specific, and accurately describes the primary change: adding ECDSA signing and verification support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@example/local_example.js`:
- Around line 4-7: Replace the relative require("../") that imports SignedXml
with the package name require("xml-crypto") so the example demonstrates end-user
usage; locate the SignedXml import in local_example.js (currently assigned to
the SignedXml variable) and update that require to "xml-crypto" while leaving
other requires (select, DOMParser, fs) unchanged.
🧹 Nitpick comments (5)
src/signature-algorithms.ts (2)

56-84: Consider removing @ts-ignore by using crypto.createPrivateKey.

The commented-out fix on lines 59-60 would work. Using crypto.createPrivateKey(privateKey) ensures the key is properly parsed and the resulting KeyObject type is compatible with the dsaEncoding option in TypeScript's type definitions.

♻️ Proposed fix to remove `@ts-ignore`
 export class EcdsaSha256 implements SignatureAlgorithm {
   getSignature = createOptionalCallbackFunction(
     (signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string => {
-      // Maybe the fix for ts-ignore below?
-      // const parsedPrivateKey = crypto.createPrivateKey(privateKey);
+      const parsedPrivateKey = crypto.createPrivateKey(privateKey);
       const signer = crypto.createSign("SHA256");
       signer.update(signedInfo);
-      // `@ts-ignore`
-      const res = signer.sign({ key: privateKey, dsaEncoding: 'ieee-p1363' }, "base64");
+      const res = signer.sign({ key: parsedPrivateKey, dsaEncoding: 'ieee-p1363' }, "base64");

       return res;
     },
   );

159-187: Same @ts-ignore issue applies to EcdsaSha512.

Apply the same fix as recommended for EcdsaSha256 to maintain consistency and type safety.

♻️ Proposed fix
 export class EcdsaSha512 implements SignatureAlgorithm {
   getSignature = createOptionalCallbackFunction(
     (signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string => {
-      // Maybe the fix for ts-ignore below?
-      // const parsedPrivateKey = crypto.createPrivateKey(privateKey);
+      const parsedPrivateKey = crypto.createPrivateKey(privateKey);
       const signer = crypto.createSign("SHA512");
       signer.update(signedInfo);
-      // `@ts-ignore`
-      const res = signer.sign({ key: privateKey, dsaEncoding: 'ieee-p1363' }, "base64");
+      const res = signer.sign({ key: parsedPrivateKey, dsaEncoding: 'ieee-p1363' }, "base64");

       return res;
     },
   );
example/result.xml (1)

1-1: Consider adding example/result.xml to .gitignore.

This file is generated output from running the example script. Committing generated files can cause confusion if they become stale. Alternatively, if this serves as a reference/fixture, consider renaming it to indicate it's a sample output.

example/example.js (1)

10-11: Missing semicolons - inconsistent with file style.

Lines 10-11 are missing semicolons while the rest of the file uses them consistently.

🔧 Proposed fix
-  sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"
-  sig.signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
+  sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#";
+  sig.signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
test/signature-unit-tests.spec.ts (1)

82-141: ECDSA test suite provides good coverage.

The tests properly cover both signing/verification and manipulation detection for ECDSA algorithms. The structure mirrors the RSA tests, ensuring consistent behavior.

Consider extracting the common test logic into a shared helper to reduce duplication with the RSA test block (lines 22-80), but this is optional.

,

Comment thread example/local_example.js
Comment on lines +4 to +7
const select = require("xpath").select
const dom = require("@xmldom/xmldom").DOMParser;
const SignedXml = require("../").SignedXml;
const fs = require("fs");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use package name import instead of relative path.

The import on line 6 uses a relative path (require("../")) rather than the package name. Example files should use require("xml-crypto") to demonstrate usage from an end-user's perspective, matching the pattern in example/example.js.

📦 Proposed fix
 const select = require("xpath").select
 const dom = require("@xmldom/xmldom").DOMParser;
-const SignedXml = require("../").SignedXml;
+const SignedXml = require("xml-crypto").SignedXml;
 const fs = require("fs");

Based on learnings: "Example files in the node-saml/xml-crypto repository should use require("xml-crypto") (the package name) rather than relative paths to build artifacts, since they demonstrate usage from an end-user's perspective."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const select = require("xpath").select
const dom = require("@xmldom/xmldom").DOMParser;
const SignedXml = require("../").SignedXml;
const fs = require("fs");
const select = require("xpath").select
const dom = require("@xmldom/xmldom").DOMParser;
const SignedXml = require("xml-crypto").SignedXml;
const fs = require("fs");
🤖 Prompt for AI Agents
In `@example/local_example.js` around lines 4 - 7, Replace the relative
require("../") that imports SignedXml with the package name
require("xml-crypto") so the example demonstrates end-user usage; locate the
SignedXml import in local_example.js (currently assigned to the SignedXml
variable) and update that require to "xml-crypto" while leaving other requires
(select, DOMParser, fs) unchanged.


verifySignature = createOptionalCallbackFunction(
(material: string, key: crypto.KeyLike, signatureValue: string): boolean => {
const publicKey = crypto.createPublicKey(key);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

potential for key confusion attacks i.e. rsa public key being passed here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had looked into this, but the crypto functions seem to be validating this already. Do you mind demonstrating a key confusion attack?

@ahacker1-securesaml ahacker1-securesaml Feb 16, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's best practice to add checks (for this and the other signature-algorithms) that the verification key is the correct type (ec for this one). Currently there's no security vulnerability. If a rsa public key is passed in here, node:crypto will just verify with RSA (the key isn't converted to EC).

@madaster97 madaster97 changed the title Add ECDSA Support Add ECDSA Support (#530) Apr 1, 2026
Moves basic ECDSA tests to a new, dedicated `ecdsa-signatures.spec.ts` file.
The new suite adds comprehensive test cases for various scenarios, including
key type mismatches, signature manipulation, external fixtures, and `KeyObject`
verification, significantly improving coverage and robustness.
@cjbarth

cjbarth commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

@madaster97 , I've reviewed this and created some tests that are currently failing. Please have another look at this code and see what you can do to bring it up to standard. Also, please run npm run lint:fix before your next push to clean up the errors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants